defmodule Pango.BaseRpc do
	@moduledoc """
	    进程模型:
	        msg -> Rpc, Rpc 把消息转发给 impl_woker, 由 impl_woker -> apply(m, f, a), 执行完后,根据需要返回执行结果
	    使用说明:
	        use Pango.BaseRpc, name: name(进程池注册名)
	        先启动 Rpc 进程, 再启动 children
	        [{*Rpc, [swarm_name]}] ++ *Rpc.children()
	"""

	@doc """
	    {impl_woker 进程数, impl_woker 的 max_overflow}
	    默认{4, 0}
	"""
	@callback pool_info() :: {integer, integer}

	defmacro __using__(opts) do
		quote location: :keep do
			@behaviour Pango.BaseRpc
			alias Pango.ImplWorker
			require Logger
			use GenServer
			@start_ok? :start_ok
			@flush_min 60 * 10 * 1000
			@keys  [
				:message_queue_len,
				:minor_gcs,
				:reductions
			]

			def children(parent) do
				{size, max} = pool_info()
				pool_options = [
					name: {:local, unquote(opts)[:name]},
					worker_module: ImplWorker,
					size: size,
					max_overflow: max
				]
				[
					:poolboy.child_spec(unquote(opts)[:name], pool_options, [parent])
				]
			end

			@doc """
			    name: swarm 注册名称,后续通过该名称访问
			"""
			def start_link([name]) do
				{:ok, pid} = GenServer.start_link(__MODULE__, [])
				Swarm.register_name(name, pid)
				{:ok, pid}
			end


			def start_ok?(name \\ nil) do
				name = if name == nil do
					name = __MODULE__
				else
					name
				end
				GenServer.call(Swarm.whereis_name(name), :start_ok?)
			end

			def get_workers_info() do
				for {_, pid, _, _} <- GenServer.call(unquote(opts)[:name], :get_all_workers) do
					{pid, Keyword.take(Process.info(pid), @keys)}
				end
				|> Enum.sort_by(&(elem(&1, 1)[:message_queue_len]), :desc)
			end

			def flush(name) do
				send(Swarm.whereis_name(name), :flush_statics)
			end

			def get_state(name) do
				name = if name == nil do
					name = __MODULE__
				else
					name
				end
				GenServer.call(Swarm.whereis_name(name), :state)
				|> Pango.BaseRpc.sort()
			end

			def init(_opts) do
				Process.send_after(self(), :flush_statics, @flush_min)
				{:ok, %{}}
			end

			def handle_call({:swarm, :begin_handoff}, _from, state) do
				{:reply, :ignore, state}
			end
			def handle_call(:start_ok?, _from, state) do
				{:reply, server_start_ok?(), state}
			end
			def handle_call(:state, _from, state) do
				{:reply, state, state}
			end
			def handle_call({sync_or_async, {mod, func, args}}, from, state) when is_atom(mod) and is_atom(func) do
				if Process.get(@start_ok?) || server_start_ok?() do
					cast_pool(sync_or_async, mod, func, args, from)
				else
					GenServer.reply(from, :server_starting)
				end
				{:noreply, state}
			end
			def handle_call(msg, _from, state) do
				Logger.error(inspect(msg))
				{:reply, "input_error", state}
			end

			def handle_cast({:swarm, :end_handoff, old_state}, state) do
				{:noreply, old_state}
			end

			def handle_cast({:swarm, :resolve_conflict, _delay}, state) do
				{:noreply, state}
			end
			def handle_cast({mod, func, args}, state) when is_atom(mod) and is_atom(func) do
				if Process.get(@start_ok?) || server_start_ok?() do
					cast_pool(:cast, mod, func, args, nil)
				end
				{:noreply, state}
			end
			def handle_cast(_msg, state) do
				{:noreply, state}
			end

			def handle_info({:swarm, :die}, state) do
				{:stop, :shutdown, state}
			end
			def handle_info({:push_statics, statics_data}, state) do
				{:noreply, Pango.BaseRpc.statics(state, statics_data)}
			end
			def handle_info(:flush_statics, state) do
				File.exists?("./statics") || File.mkdir("./statics")
				File.write!(Pango.BaseRpc.get_statics_file(), Jason.encode!(state))
				Process.send_after(self(), :flush_statics, @flush_min)
				{:noreply, state}
			end
			# 用于多线程导致的混乱问题,不能用于耗时的操作
			def handle_info({:do, m, f, a}, state) do
				apply(m, f, a)
				{:noreply, state}
			end
			def handle_info(msg, state) do
				Logger.error(inspect(msg))
				{:noreply, state}
			end

			def terminate(reason, state) do
				Logger.error(inspect(reason))
				File.exists?("./statics") || File.mkdir("./statics")
				File.write!(Pango.BaseRpc.get_statics_file(), Jason.encode!(state))
				:ok
			end

			def cast_pool(sync_or_async, mod, func, args, from) do
				try do
					worker = :poolboy.checkout(unquote(opts)[:name], false, 5000)
					worker == :full && throw({:error, "please_wait"})
					GenServer.cast(worker, {sync_or_async, unquote(opts)[:name], worker, mod, func, args, from})
				rescue
					err ->
						Logger.error(inspect({mod, func, args, err, __STACKTRACE__}))
						from != nil && GenServer.reply(from, "unexpected_error")
				catch
					{:error, err} ->
						from != nil && GenServer.reply(from, err)
				end
			end

			defp server_start_ok?() do
				bool = Pango.AppProtocol.start_ok?()
				bool && Process.put(@start_ok?, bool)
				bool
			end

			def pool_info() do
				{4, 0}
			end

			defoverridable pool_info: 0
		end
	end


	def get_statics_file() do
		{{y, m, d}, {h, min, _s}} = :calendar.local_time
		"./statics/#{y}-#{m}-#{d}_#{h}_#{min}.json"
	end

	# %{mod => %{interface1 => %{"time" => 0, "count" => 0}}}
	def statics(state, data) do
		Enum.reduce(
			data,
			state,
			fn ({mod, interfaces}, acc) ->
				value = Enum.reduce(
					interfaces,
					Map.get(acc, mod, %{}),
					fn ({interface, info}, cur_info) ->
						value = Pango.ImplWorker.merge(info, Map.get(cur_info, interface, %{}))
						Map.put(cur_info, interface, value)
					end
				)
				Map.put(acc, mod, value)
			end
		)
	end



	def sort(state) do
		state
		|> Enum.map(
				 fn ({mod, interfaces}) ->
					 {
						 mod,
						 Enum.map(
							 interfaces,
							 fn ({interface, %{"count" => count, "time" => time}}) ->
								 %{
									 interface => %{
										 "count" => count,
										 "avg" => round(time / count)
									 }
								 }
							 end
						 )
						 |> Enum.sort(
									fn (h1, h2) ->
										Map.get(hd(Map.values(h1)), "avg") > Map.get(hd(Map.values(h2)), "avg")
									end
								)
					 }
				 end
			 )
	end


end
